{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:31.713933Z",
     "start_time": "2025-04-11T04:01:27.639648Z"
    }
   },
   "source": [
    "# 线性回归简洁实现\n",
    "\n",
    "import numpy as np\n",
    "import torch\n",
    "from torch.utils import data\n",
    "from d2l import torch as d2l\n",
    "\n",
    "# 定义真实的权重向量 true_w，它是一个二维张量，这里的 [2, -3.4] 表示两个特征对应的真实权重\n",
    "# 在实际的线性回归模型中，我们希望通过训练让模型学习到接近这个真实权重的值\n",
    "true_w = torch.tensor([2, -3.4])\n",
    "# 定义真实的偏置项 true_b，它是一个标量，在线性回归模型中用于调整模型的截距\n",
    "# 同样，我们希望模型通过训练学习到接近这个真实偏置的值\n",
    "true_b = 4.2\n",
    "\n",
    "# 调用 d2l 库中的 synthetic_data 函数生成合成数据\n",
    "# 该函数根据给定的真实权重 true_w、真实偏置 true_b 和样本数量 1000 生成特征矩阵 features 和对应的标签向量 labels\n",
    "# 生成的数据满足线性关系 y = Xw + b 并添加了一定的噪声，模拟真实世界中的数据情况\n",
    "features, labels = d2l.synthetic_data(true_w, true_b, 1000)"
   ],
   "outputs": [],
   "execution_count": 1
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:31.729470Z",
     "start_time": "2025-04-11T04:01:31.721020Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def load_array(data_arrays, batch_size, is_train=True):  #@save\n",
    "    \"\"\"\n",
    "    构造一个 PyTorch 数据迭代器，用于批量加载数据。\n",
    "\n",
    "    参数:\n",
    "    data_arrays (tuple): 包含多个张量的元组，通常是特征张量和标签张量，例如 (features, labels)。\n",
    "    batch_size (int): 每个批次加载的数据样本数量。\n",
    "    is_train (bool, 可选): 表示是否用于训练阶段。默认为 True，训练阶段会打乱数据顺序。\n",
    "\n",
    "    返回:\n",
    "    torch.utils.data.DataLoader: 一个 PyTorch 数据加载器对象，可用于迭代加载数据批次。\n",
    "    \"\"\"\n",
    "    # data.TensorDataset 用于将多个张量包装成一个数据集对象\n",
    "    # *data_arrays 是解包操作，将元组中的每个张量分别传入 TensorDataset\n",
    "    # 这样可以将特征张量和标签张量组合成一个数据集，方便后续处理\n",
    "    dataset = data.TensorDataset(*data_arrays)\n",
    "    # data.DataLoader 是 PyTorch 提供的数据加载器，用于从数据集中批量加载数据\n",
    "    # dataset 是要加载的数据集\n",
    "    # batch_size 是每个批次加载的数据样本数量\n",
    "    # shuffle=is_train 表示是否打乱数据顺序，如果 is_train 为 True（训练阶段），则打乱数据；否则不打乱\n",
    "    return data.DataLoader(dataset, batch_size, shuffle=is_train)\n",
    "\n",
    "# 设置每个批次加载的数据样本数量为 10\n",
    "batch_size = 10\n",
    "# 调用 load_array 函数，传入特征张量 features 和标签张量 labels，以及批次大小 batch_size\n",
    "# 得到一个数据加载器对象 data_iter，可用于迭代加载数据批次\n",
    "data_iter = load_array((features, labels), batch_size)"
   ],
   "id": "628d87fecc4b11ed",
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:31.760637Z",
     "start_time": "2025-04-11T04:01:31.734787Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 解释 next(iter(data_iter)) 这行代码\n",
    "# iter(data_iter)：\n",
    "#   iter() 是 Python 的内置函数，它的作用是将一个可迭代对象转化为迭代器。\n",
    "#   这里的 data_iter 是通过 load_array 函数创建的 DataLoader 对象，本身就是可迭代的。\n",
    "#   调用 iter(data_iter) 后，会得到一个可以按顺序逐个访问 data_iter 中元素的迭代器。\n",
    "# next(iter(data_iter))：\n",
    "#   next() 同样是 Python 的内置函数，它用于从迭代器中获取下一个元素。\n",
    "#   由于这里是第一次调用 next()，所以会从 data_iter 对应的迭代器里取出第一个小批量的数据。\n",
    "#   这个小批量数据通常是一个元组，元组里包含了当前批次的特征数据和标签数据。\n",
    "next(iter(data_iter))"
   ],
   "id": "55bd40682e798b6",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[tensor([[ 0.0683, -1.3827],\n",
       "         [-0.1180,  0.2981],\n",
       "         [ 1.1595, -0.8616],\n",
       "         [-0.9346, -0.0921],\n",
       "         [-0.8819,  0.4858],\n",
       "         [-0.6076, -0.1765],\n",
       "         [ 0.1201,  0.7298],\n",
       "         [ 1.8049,  0.7658],\n",
       "         [-0.5424, -1.1579],\n",
       "         [-1.3092,  0.1620]]),\n",
       " tensor([[9.0302],\n",
       "         [2.9514],\n",
       "         [9.4469],\n",
       "         [2.6487],\n",
       "         [0.7788],\n",
       "         [3.6052],\n",
       "         [1.9465],\n",
       "         [5.1942],\n",
       "         [7.0737],\n",
       "         [1.0154]])]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:31.915701Z",
     "start_time": "2025-04-11T04:01:31.901461Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# nn是神经网络的缩写\n",
    "# 从 torch 库中导入 nn 模块\n",
    "# nn 模块是 PyTorch 中用于构建神经网络的核心模块，提供了各种神经网络层、损失函数、优化器等组件\n",
    "from torch import nn\n",
    "\n",
    "# 创建一个神经网络模型\n",
    "# nn.Sequential 是一个容器类，用于按顺序组合多个神经网络层，形成一个序列化的模型\n",
    "# 在这个容器中，每一层的输出会作为下一层的输入\n",
    "# nn.Linear(2, 1) 是一个全连接层（也称为线性层）\n",
    "# 第一个参数 2 表示该层的输入特征数量，意味着输入的张量应该有 2 个特征维度\n",
    "# 第二个参数 1 表示该层的输出特征数量，即该层会将输入的 2 维特征映射到 1 维输出\n",
    "# 因此，这里创建的 net 模型是一个简单的线性回归模型，它接受 2 维的输入特征，经过线性变换后输出 1 维的结果\n",
    "net = nn.Sequential(nn.Linear(2, 1))"
   ],
   "id": "33cddb0f06083064",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:31.961782Z",
     "start_time": "2025-04-11T04:01:31.947962Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 前面已经定义了 net 模型，net 是一个 nn.Sequential 对象，包含了一个线性层 nn.Linear(2, 1)\n",
    "\n",
    "# 初始化线性层的权重\n",
    "# net[0] 表示从 nn.Sequential 容器中取出第一个（也是这里唯一的）神经网络层，即我们定义的 nn.Linear(2, 1) 层\n",
    "# .weight 是该线性层的权重参数，它是一个可训练的张量\n",
    "# .data 用于直接访问该张量的数据部分，避免在操作时触发自动求导机制\n",
    "# .normal_(0, 0.01) 是 PyTorch 张量的一个原地操作方法，将该张量的数据用均值为 0、标准差为 0.01 的正态分布进行初始化\n",
    "# 这样做的目的是为模型的权重参数赋予一个合适的初始值，有助于模型在训练初期更稳定地学习\n",
    "net[0].weight.data.normal_(0, 0.01)\n",
    "\n",
    "# 初始化线性层的偏置\n",
    "# 同样，net[0] 取出线性层\n",
    "# .bias 是该线性层的偏置参数，也是一个可训练的张量\n",
    "# .data 用于直接访问该张量的数据部分，避免触发自动求导\n",
    "# .fill_(0) 是 PyTorch 张量的一个原地操作方法，将该张量的数据全部填充为 0\n",
    "# 在很多情况下，将偏置初始化为 0 是一种常见且有效的做法\n",
    "net[0].bias.data.fill_(0)"
   ],
   "id": "f6b57842e6b1bac9",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([0.])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:31.992302Z",
     "start_time": "2025-04-11T04:01:31.978775Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 从 torch 库中导入的 nn 模块里，有用于构建神经网络的各种组件。\n",
    "# nn.MSELoss() 是 PyTorch 提供的一个类，用于创建均方误差损失函数对象。\n",
    "# 均方误差（Mean Squared Error, MSE）损失函数常用于回归问题，\n",
    "# 其计算方式是预测值与真实值之间差值的平方的平均值。\n",
    "# 具体公式为：MSE = (1/n) * Σ(y_pred - y_true)^2，\n",
    "# 其中 n 是样本数量，y_pred 是模型的预测值，y_true 是真实标签值。\n",
    "# 这里将创建好的均方误差损失函数对象赋值给变量 loss，\n",
    "# 在后续训练模型时，可以使用 loss 来计算模型预测值与真实值之间的损失，\n",
    "# 进而根据损失值来更新模型的参数，以达到优化模型的目的。\n",
    "loss = nn.MSELoss()"
   ],
   "id": "cf00352d03c2334b",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:32.023132Z",
     "start_time": "2025-04-11T04:01:32.008865Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# torch.optim 是 PyTorch 中用于实现各种优化算法的模块，这些优化算法用于在模型训练过程中更新模型的参数。\n",
    "# SGD 即随机梯度下降（Stochastic Gradient Descent），是一种常用的优化算法。\n",
    "# 它通过计算损失函数关于模型参数的梯度，然后沿着梯度的反方向更新参数，以逐步减小损失函数的值。\n",
    "\n",
    "# net.parameters() 是 PyTorch 中 nn.Module 类的一个方法，net 是之前定义的神经网络模型（如 nn.Sequential(nn.Linear(2, 1))）。\n",
    "# 该方法会返回一个包含模型中所有可训练参数（即需要学习的权重和偏置）的迭代器。\n",
    "# 这里将 net 模型的所有可训练参数传递给 SGD 优化器，意味着优化器会对这些参数进行更新。\n",
    "\n",
    "# lr=0.03 是设置学习率（learning rate）为 0.03。\n",
    "# 学习率是一个超参数，它控制着每次参数更新的步长大小。\n",
    "# 较大的学习率可能使模型收敛更快，但也可能导致模型无法收敛或在最优解附近震荡；\n",
    "# 较小的学习率则可能使收敛速度变慢，但能更稳定地接近最优解。\n",
    "\n",
    "# 最后，将创建好的 SGD 优化器对象赋值给变量 trainer，\n",
    "# 在后续的模型训练过程中，可以使用 trainer 来更新模型的参数。\n",
    "trainer = torch.optim.SGD(net.parameters(), lr=0.03)"
   ],
   "id": "bac311c732fc369b",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:32.132229Z",
     "start_time": "2025-04-11T04:01:32.040193Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 设置训练的轮数，即整个数据集会被模型遍历的次数\n",
    "# 每一轮中，模型会使用所有的数据进行一次完整的学习和参数更新\n",
    "num_epochs = 3\n",
    "\n",
    "# 外层循环控制训练的轮数\n",
    "for epoch in range(num_epochs):\n",
    "    # 内层循环通过 data_iter 函数将数据集分割成小批量数据，每次迭代处理一个小批量\n",
    "    for X, y in data_iter:\n",
    "        # 前向传播：将输入数据 X 传入模型 net 中，得到模型的预测值\n",
    "        # 然后使用损失函数 loss 计算预测值与真实标签 y 之间的损失\n",
    "        l = loss(net(X), y)\n",
    "\n",
    "        # 梯度清零：在每次计算梯度之前，需要将优化器中所有参数的梯度清零\n",
    "        # 因为 PyTorch 会累积梯度，如果不清零，梯度会不断累加，导致结果错误\n",
    "        trainer.zero_grad()\n",
    "\n",
    "        # 反向传播：计算损失函数关于模型参数的梯度\n",
    "        # 调用 backward() 方法后，PyTorch 会自动计算每个可训练参数的梯度\n",
    "        l.backward()\n",
    "\n",
    "        # 参数更新：根据计算得到的梯度，使用优化器 trainer 来更新模型的参数\n",
    "        # 优化器会根据预设的优化算法（如 SGD）和学习率，对参数进行更新\n",
    "        trainer.step()\n",
    "\n",
    "    # 在每一轮训练结束后，计算整个训练数据集上的损失\n",
    "    # 将所有特征数据 features 传入模型 net 中，得到预测值\n",
    "    # 再使用损失函数 loss 计算预测值与真实标签 labels 之间的损失\n",
    "    l = loss(net(features), labels)\n",
    "\n",
    "    # 打印当前轮数和对应的训练损失，保留小数点后 6 位\n",
    "    print(f'epoch {epoch + 1}, loss {l:f}')"
   ],
   "id": "c75877fade4da712",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 1, loss 0.000235\n",
      "epoch 2, loss 0.000101\n",
      "epoch 3, loss 0.000101\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-11T04:01:32.164509Z",
     "start_time": "2025-04-11T04:01:32.149792Z"
    }
   },
   "cell_type": "code",
   "source": [
    "w = net[0].weight.data\n",
    "print('w的估计误差：', true_w - w.reshape(true_w.shape))\n",
    "b = net[0].bias.data\n",
    "print('b的估计误差：', true_b - b)"
   ],
   "id": "3c0c4ef21dfa1638",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "w的估计误差： tensor([3.7026e-04, 5.6505e-05])\n",
      "b的估计误差： tensor([0.0004])\n"
     ]
    }
   ],
   "execution_count": 9
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
